package crmdna.user;

import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import com.googlecode.objectify.ObjectifyFilter;
import com.googlecode.objectify.Work;

import crmdna.client.Client;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.sequence.Sequence;
import crmdna.sequence.Sequence.SequenceType;

public class UserCore {

	public static final String SUPER_USER = "sathyanarayanant@gmail.com";

	public static class UserProp {
		public String email;
		public TreeSet<String> privileges = new TreeSet<>();
		// format is: <resource type>||<resource id>||<action>
		// eg: group||1||read, group||2||write

		public long groupId;
		public long userId;
	}

	private static final String DELIMITER = "||";

	static UserProp create(String namespace, String email, long groupId) {

		Utils.ensureValidEmail(email);
		email = email.toLowerCase();

		UserEntity entity = ofy(namespace).load().type(UserEntity.class)
				.id(email).get();

		if (null != entity)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
					"There is already an user with email [" + email + "]");

		UserEntity userEntity = new UserEntity();
		userEntity.email = email;
		userEntity.userId = Sequence.getNext(namespace, SequenceType.USER);
		userEntity.groupId = groupId;

		ofy(namespace).save().entity(userEntity).now();

		return userEntity.toProp();
	}

	static UserProp updateEmail(String namespace, String existingEmail,
			String newEmail) {

		Utils.ensureValidEmail(newEmail);

		existingEmail = existingEmail.toLowerCase();
		newEmail = newEmail.toLowerCase();

		UserEntity existingEntity = UserCore.safeGet(namespace, existingEmail);

		if (existingEmail.equals(newEmail))
			return existingEntity.toProp();

		// newEmail should not already exist
		if (null != get(namespace, newEmail))
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
					"There is already an user with email [" + newEmail + "]");

		UserEntity newEntity = new UserEntity();
		newEntity.email = newEmail;
		newEntity.userId = existingEntity.userId;
		newEntity.groupId = existingEntity.groupId;
		newEntity.privileges = existingEntity.privileges;

		ofy(namespace).save().entity(newEntity);
		ofy(namespace).delete().entity(existingEntity);
		ObjectifyFilter.complete();

		return newEntity.toProp();
	}

	static UserProp updateGroup(final String namespace, final String email,
			final long newGroupId) {

		// email should be present
		safeGet(namespace, email.toLowerCase());

		UserProp userProp = ofy(namespace).transact(new Work<UserProp>() {
			@Override
			public UserProp run() {
				UserEntity userEntity = ofy(namespace).load()
						.type(UserEntity.class).id(email.toLowerCase()).get();
				if (null == userEntity)
					throw new APIException(
							Status.ERROR_RESOURCE_NOT_FOUND,
							"There is no user with email [" + email + "]");

				userEntity.groupId = newGroupId;
				ofy(namespace).save().entity(userEntity).now();
				return userEntity.toProp();
			}
		});

		return userProp;
	}

	public static UserEntity get(String namespace, String email) {

		email = email.toLowerCase();
		UserEntity entity = ofy(namespace).load().type(UserEntity.class)
				.id(email).get();

		if (entity == null)
			return null;

		return entity;
	}

	public static UserEntity safeGet(String client, String email) {
		UserEntity userEntity = get(client, email);

		if (null == userEntity)
			throw new APIException(Status.ERROR_INVALID_USER, "User [" + email
					+ "] is not valid");

		return userEntity;
	}

	public static List<UserProp> getAll(String namespace) {
		Client.ensureValid(namespace);

		List<UserEntity> entities = ofy(namespace).load()
				.type(UserEntity.class).order("email").list();

		List<UserProp> props = new ArrayList<>();
		for (UserEntity entity : entities) {
			props.add(entity.toProp());
		}

		return props;
	}

	public static List<UserProp> getAllForGroup(String namespace, long groupId) {

		List<UserProp> all = getAll(namespace);

		List<UserProp> userProps = new ArrayList<>();
		for (UserProp userProp : all) {
			if (userProp.groupId == groupId)
				userProps.add(userProp);
		}

		return userProps;
	}

	static UserProp addOrDeletePrivilege(final String namespace,
			final String email, final String resourceType,
			final long resourceId, final String action, final boolean add) {

		safeGet(namespace, email.toLowerCase()); // ensures user is valid

		UserProp userProp = ofy(namespace).transact(new Work<UserProp>() {

			@Override
			public UserProp run() {
				UserEntity userEntity = ofy(namespace).load()
						.type(UserEntity.class).id(email.toLowerCase()).get();

				if (null == userEntity)
					throw new APIException(Status.ERROR_INVALID_USER,
							"Invalid user [" + email + "]");

				String privilege = getPrivilege(resourceType, resourceId,
						action);

				boolean change;
				if (add)
					change = userEntity.privileges.add(privilege);
				else
					change = userEntity.privileges.remove(privilege);

				if (change)
					ofy(namespace).save().entity(userEntity).now();

				return userEntity.toProp();
			}
		});

		return userProp;
	}

	static Set<Long> getResources(String namespace, String email,
			String resourceType, String action) {

		UserEntity entity = safeGet(namespace, email.toLowerCase()); // ensures
																		// user
																		// is
																		// valid

		Set<Long> resources = new HashSet<>();
		for (String privilege : entity.privileges) {
			if ((privilege != null) && privilege.contains(resourceType)
					&& privilege.contains(action)) {
				// format is: <resource type>||<resource id>||<action>
				long resource = getResourceFromPrivilege(privilege);
				resources.add(resource);
			}
		}

		return resources;
	}

	static long getResourceFromPrivilege(String privilege) {
		String resource = privilege.split("\\|\\|")[1];
		return Long.parseLong(resource);
	}

	static UserProp clonePrivileges(final String namespace,
			final String sourceEmail, final String targetEmail) {

		if (null == sourceEmail)
			Utils.throwIncorrectSpecException("sourceUser cannot be null");

		if (null == targetEmail)
			Utils.throwIncorrectSpecException("targetUser cannot be null");

		safeGet(namespace, sourceEmail.toLowerCase());
		safeGet(namespace, targetEmail.toLowerCase());

		UserProp userProp = ofy(namespace).transact(new Work<UserProp>() {

			@Override
			public UserProp run() {
				UserEntity sourceEntity = ofy(namespace).load()
						.type(UserEntity.class).id(sourceEmail.toLowerCase())
						.get();
				if (null == sourceEntity)
					throw new APIException(
							Status.ERROR_RESOURCE_NOT_FOUND,
							"Source email [" + sourceEmail + "] does not exist");

				UserEntity targetEntity = ofy(namespace).load()
						.type(UserEntity.class).id(targetEmail.toLowerCase())
						.get();
				if (null == targetEntity)
					throw new APIException(
							Status.ERROR_RESOURCE_NOT_FOUND,
							"Target email [" + targetEmail + "] does not exist");

				targetEntity.privileges = sourceEntity.privileges;
				ofy(namespace).save().entity(targetEntity).now();
				return targetEntity.toProp();
			}
		});

		return userProp;
	}

	public static void ensureValidUser(String client, String login) {

		if (null == login)
			throw new APIException(Status.ERROR_LOGIN_REQUIRED,
					"Email cannot be null or empty");

		if (login.equals(SUPER_USER))
			return;

		login = login.toLowerCase();
		UserEntity userEntity = ofy(client).load().type(UserEntity.class)
				.id(login).get();

		if (null == userEntity)
			throw new APIException(Status.ERROR_INVALID_USER, "Invalid user ["
					+ login + "]");
	}

	static void ensureAccess(String client, String login, String resourceType,
			long resourceId, String action) {

		if (null == login)
			throw new APIException(Status.ERROR_LOGIN_REQUIRED,
					"Email cannot be null or empty");

		if (login.equals(SUPER_USER))
			return;

		login = login.toLowerCase();
		UserEntity userEntity = ofy(client).load().type(UserEntity.class)
				.id(login).get();

		if (null == userEntity)
			throw new APIException(Status.ERROR_INVALID_USER, "Invalid user ["
					+ login + "]");

		String privilege = getPrivilege(resourceType, resourceId, action);
		if (!userEntity.privileges.contains(privilege))
			throw new APIException(
					Status.ERROR_INSUFFICIENT_PERMISSION,
					"User ["
							+ login
							+ "] does not have the required privilage - ["
							+ privilege
							+ "]. (Privilege format is <Resource type>||<Resource id>||<Action>)");
	}

	static String getPrivilege(String resourceType, long resourceId,
			String action) {

		return resourceType.toString() + DELIMITER + resourceId + DELIMITER
				+ action.toString();
	}
}